package org.apache.rocketmq.namesrv;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.rocketmq.common.MQVersion;
import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.common.namesrv.NamesrvConfig;
import org.apache.rocketmq.logging.org.slf4j.Logger;
import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
import org.apache.rocketmq.remoting.netty.NettyClientConfig;
import org.apache.rocketmq.remoting.netty.NettyServerConfig;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
import org.apache.rocketmq.srvutil.ServerUtil;
import org.apache.rocketmq.srvutil.ShutdownHookThread;

import java.io.BufferedInputStream;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Properties;
import java.util.concurrent.Callable;



/**
 * @课程描述:从零带你写框架系列中的课程，整个系列包含netty，xxl-job，rocketmq，nacos，sofajraft，spring，springboot，disruptor，编译器，虚拟机等等。
 * @author：陈清风扬，个人微信号：chenqingfengyangjj。
 * @date:2024/12/25
 * @方法描述：Namesrv模块的启动类
 */
public class NamesrvStartup {

    private final static Logger log = LoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME);

    //专门定义了一个控制台日志记录器
    private final static Logger logConsole = LoggerFactory.getLogger(LoggerName.NAMESRV_CONSOLE_LOGGER_NAME);

    //配置信息对象，这个配置信息对象封装的就是用户定义的配置信息中的所有信息
    private static Properties properties = null;

    //封装了Namesrv需要的配置信息的成员变量
    private static NamesrvConfig namesrvConfig = null;

    //封装了Netty服务端需要的配置信息的成员变量
    private static NettyServerConfig nettyServerConfig = null;
    //封装了Netty客户端需要的配置信息的成员变量
    private static NettyClientConfig nettyClientConfig = null;

    public static void main(String[] args) {
        main0(args);
        //这里本来有启动ControllerManager控制管理器的操作，但是源码中默认配置是不启用控制管理器的
        //所以我就把下面这个操作注释了，
        //controllerManagerMain();
    }


    /**
     * @课程描述:从零带你写框架系列中的课程，整个系列包含netty，xxl-job，rocketmq，nacos，sofajraft，spring，springboot，disruptor，编译器，虚拟机等等。
     * @author：陈清风扬，个人微信号：chenqingfengyangjj。
     * @date:2024/12/25
     * @方法描述：启动Namesrv的入口方法
     */
    public static NamesrvController main0(String[] args) {
        try {
            //解析命令行参数信息
            parseCommandlineAndConfigFile(args);
            //创建并启动NamesrvController控制器对象
            NamesrvController controller = createAndStartNamesrvController();
            return controller;
        } catch (Throwable e) {
            e.printStackTrace();
            System.exit(-1);
        }
        return null;
    }




    /**
     * @课程描述:从零带你写框架系列中的课程，整个系列包含netty，xxl-job，rocketmq，nacos，sofajraft，spring，springboot，disruptor，编译器，虚拟机等等。
     * @author：陈清风扬，个人微信号：chenqingfengyangjj。
     * @date:2024/12/25
     * @方法描述：解析命令行参数信息的方法
     */
    public static void parseCommandlineAndConfigFile(String[] args) throws Exception {
        //向系统环境变量中设置当前程序的版本
        System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, Integer.toString(MQVersion.CURRENT_VERSION));
        //构建Options对象，这个Options对象中持有了全部的命令行参数信息
        //参数信息会被封装在Option对象中，而Option对象就会被当前创建的options对象保存
        Options options = ServerUtil.buildCommandlineOptions(new Options());
        //解析命令行参数信息
        CommandLine commandLine = ServerUtil.parseCmdLine("mqnamesrv", args, buildCommandlineOptions(options), new DefaultParser());
        //参数信息解析失败则直接退出程序
        if (null == commandLine) {
            System.exit(-1);
            return;
        }
        //创建NamesrvConfig配置信息对象
        namesrvConfig = new NamesrvConfig();
        //创建Netty服务端要使用的配置信息对象
        nettyServerConfig = new NettyServerConfig();
        //设置Netty服务端监听的默认端口，这里设置的默认端口是9876
        //但用户可以在配置文件中更改服务端监听的端口号，具体例子我在文章中为大家展示了
        nettyServerConfig.setListenPort(9876);

        //创建Netty客户端要使用的配置信息对象
        nettyClientConfig = new NettyClientConfig();
        //判断命令行中是否设置了-c命令，-c命令就是用户自己定义的程序配置文件的路径
        //我在文章中给大家展示的例子，就把定义的namesrv.properties配置文件的路径定义为了D:\code\cqfy-mq\mq-01\common\src\main\resources\namesrv.properties
        if (commandLine.hasOption('c')) {
            //根据命令行的命令，得到对应的value值，也就是得到对应的namesrv.properties配置文件的路径
            String file = commandLine.getOptionValue('c');
            //对配置文件路径判空
            if (file != null) {
                //如果不为空则把使用输入流把配置文件读取到内存中
                InputStream in = new BufferedInputStream(Files.newInputStream(Paths.get(file)));
                //创建properties对象用来封装配置文件中的配置信息
                properties = new Properties();
                //把配置文件中的内容加载到properties配置对象中
                properties.load(in);
                //把properties对象中封装的配置信息设置到namesrvConfig对象中
                MixAll.properties2Object(properties, namesrvConfig);
                //把properties对象中封装的配置信息设置到nettyServerConfig对象中
                MixAll.properties2Object(properties, nettyServerConfig);
                //把properties对象中封装的配置信息设置到nettyClientConfig对象中
                MixAll.properties2Object(properties, nettyClientConfig);
                //把用户定义得配置文件的路径设置到namesrvConfig配置对象中
                namesrvConfig.setConfigStorePath(file);
                //在控制台输出配置信息加载成功的标志
                System.out.printf("load config properties file OK, %s%n", file);
                //关闭文件输入流
                in.close();
            }
        }
        //把命令行参数信息转换为Properties对象，然后把Properties对象中的内容设置到namesrvConfig对象中
        MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), namesrvConfig);
        //-p命令就是打印配置信息到控制台，如果用户输入了-p命令，那么接下来就把namesrvConfig、nettyServerConfig配置信息输出到控制台
        if (commandLine.hasOption('p')) {
            MixAll.printObjectProperties(logConsole, namesrvConfig);
            MixAll.printObjectProperties(logConsole, nettyServerConfig);
            MixAll.printObjectProperties(logConsole, nettyClientConfig);
            //注意，这里输出完控制信息就直接退出程序了
            System.exit(0);
        }
        //判断用户是否设置了ROCKETMQ_HOME环境变量，如果没有设置，程序会自动退出
        if (null == namesrvConfig.getRocketmqHome()) {
            System.out.printf("Please set the %s variable in your environment to match the location of the RocketMQ installation%n", MixAll.ROCKETMQ_HOME_ENV);
            System.exit(-2);
        }
        //记录namesrvConfig配置信息到日志中
        MixAll.printObjectProperties(log, namesrvConfig);
        //记录namesrvConfig配置信息到日志中
        MixAll.printObjectProperties(log, nettyServerConfig);
    }



    /**
     * @课程描述:从零带你写框架系列中的课程，整个系列包含netty，xxl-job，rocketmq，nacos，sofajraft，spring，springboot，disruptor，编译器，虚拟机等等。
     * @author：陈清风扬，个人微信号：chenqingfengyangjj。
     * @date:2024/12/25
     * @方法描述：创建并启动NamesrvController控制器的方法
     */
    public static NamesrvController createAndStartNamesrvController() throws Exception {
        //创建NamesrvController对象
        NamesrvController controller = createNamesrvController();
        //启动NamesrvController对象，注意，这里启动的时候，程序内的所有配置信息类都已经更新完毕了
        start(controller);
        //得到Netty服务器的配置信息
        NettyServerConfig serverConfig = controller.getNettyServerConfig();
        //格式化配置信息，得到Netty服务器的IP地址和端口号
        String tip = String.format("The Name Server boot success. serializeType=%s, address %s:%d",
                RemotingCommand.getSerializeTypeConfigInThisServer(),
                serverConfig.getBindAddress(),
                serverConfig.getListenPort());
        //记录程序启动日志
        log.info(tip);
        //把启动信息输出到控制台
        System.out.printf("%s%n", tip);
        return controller;
    }



    /**
     * @课程描述:从零带你写框架系列中的课程，整个系列包含netty，xxl-job，rocketmq，nacos，sofajraft，spring，springboot，disruptor，编译器，虚拟机等等。
     * @author：陈清风扬，个人微信号：chenqingfengyangjj。
     * @date:2024/12/25
     * @方法描述：创建NamesrvController对象的方法
     */
    public static NamesrvController createNamesrvController() {
        final NamesrvController controller = new NamesrvController(namesrvConfig, nettyServerConfig, nettyClientConfig);
        controller.getConfiguration().registerConfig(properties);
        return controller;
    }


    /**
     * @课程描述:从零带你写框架系列中的课程，整个系列包含netty，xxl-job，rocketmq，nacos，sofajraft，spring，springboot，disruptor，编译器，虚拟机等等。
     * @author：陈清风扬，个人微信号：chenqingfengyangjj。
     * @date:2024/12/25
     * @方法描述：启动NamesrvController控制器的方法
     */
    public static NamesrvController start(final NamesrvController controller) throws Exception {
        //对控制器做判空处理
        if (null == controller) {
            throw new IllegalArgumentException("NamesrvController is null");
        }
        //初始化控制器对象，初始化的过程会把Namesrv内部要使用的各个组件初始化完毕
        boolean initResult = controller.initialize();
        if (!initResult) {
            controller.shutdown();
            System.exit(-3);
        }
        //添加JVM关闭时的钩子函数，在程序终止的时候，也停止Namesrv内部各个模块工作
        Runtime.getRuntime().addShutdownHook(new ShutdownHookThread(log, (Callable<Void>) () -> {controller.shutdown();
            return null;
        }));
        //启动NamesrvController控制器，其实就是启动了Netty构建的Namesrv的服务端
        controller.start();
        return controller;
    }



    public static void shutdown(final NamesrvController controller) {
        controller.shutdown();
    }


    public static Options buildCommandlineOptions(final Options options) {

        Option opt = new Option("c", "configFile", true, "Name server config properties file");
        opt.setRequired(false);
        options.addOption(opt);
        opt = new Option("p", "printConfigItem", false, "Print all config items");
        opt.setRequired(false);
        options.addOption(opt);
        return options;
    }


    public static Properties getProperties() {
        return properties;
    }

}
